package opencm;

import java.io.Serializable;
import java.util.Arrays;
import java.util.List;

import org.apache.commons.lang3.Validate;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.joda.time.DateTime;
import org.joda.time.Duration;
import org.joda.time.Interval;
import org.joda.time.LocalDate;

public final class Agendamento implements Serializable {

	private final Tipo tipo;
	private final Medico medico;
	private final Especialidade especialidade;
	private final DateTime inicio;
	private final Duration duracao;
	private final LocalDate expiraEm;

	private static final long serialVersionUID = 1L;

	public enum Tipo {
		MEDICO("Por Médico"),
		ESPECIALIDADE("Por Especialidade"),
		ESPERA_MEDICO("Espera Por Médico"),
		ESPERA_ESPECIALIDADE("Espera Por Especialidade");
		
		private final String desc;

		Tipo(String desc) {
			this.desc = desc;
		}

		@Override
		public String toString() {
			return this.desc;
		}

		public static List<Tipo> toList() {
			return Arrays.asList(values());
		}
	}
	
	@Override
	public boolean equals(Object obj) {
		if (obj == null)
			return false;
		if (obj == this)
			return true;
		if (obj instanceof Agendamento) {
			Agendamento a = (Agendamento) obj;
			return new EqualsBuilder()
				.append(getMedico(), a.getMedico())
				.append(getInicio(), a.getInicio())
				.append(getDuracao(), a.getDuracao())
				.isEquals();
		}
		return false;
	}
	
	@Override
	public int hashCode() {
		return new HashCodeBuilder()
			.append(getMedico())
			.append(getInicio())
			.append(getDuracao())
			.toHashCode();
	}
	
	@Override
	public String toString() {
		return String.format("Agendamento para %s em %s", getMedico().toString(), getInicio().toString());
	}

	/**
	 * Retorna um boolean se eh um agendamento em espera ou nao.
	 * @return Boolean.
	 */
	public boolean isEmEspera() {
		return this.tipo.equals(Tipo.ESPERA_ESPECIALIDADE) || this.tipo.equals(Tipo.ESPERA_MEDICO);
	}
	
	/**
	 * Retorna o tipo do agendamento.
	 * @return Tipo do agendamento.
	 */
	public Tipo getTipo() {
		return this.tipo;
	}

	/**
	 * Retorna o medico do agendamento.
	 * @return Medico do agendamento.
	 */
	public Medico getMedico() {
		return this.medico;
	}

	/**
	 * Retorna o horario inicial do agendamento.
	 * @return Hora de inicio do agendamento.
	 */
	public DateTime getInicio() {
		return this.inicio;
	}

	/**
	 * Retorna a duracao do agendamento.
	 * @return Duracao do agendamento.
	 */
	public Duration getDuracao() {
		return this.duracao;
	}
	
	/**
	 * Retorna a especialidade marcada no agendamento.
	 * @return Especialidade.
	 */
	public Especialidade getEspecialidade() {
		return especialidade;
	}

	/**
	 * Retorna o LocalDate em que o agendamento em espera ira expirar.
	 * @return LocalDate.
	 */
	public LocalDate getExpiraEm() {
		return expiraEm;
	}

	/**
	 * Retorna o intervalo ocupado pelo agendamento..
	 * Return Interval.
	 */
	public Interval toInterval() {
		assert !isEmEspera();
		return new Interval(getInicio(), getDuracao());
	}
	
	/**
	 * Retorna um novo Agendamento criado a partir de um medico.
	 * @param medico Medico marcado para o agendamento.
	 * @param inicio Inicio do horario a ser agendado.
	 * @param duracao Duracao do horario a ser agendado.
	 * @return Um novo agendamento criado a partir do medico.
	 */
	public static Agendamento criarAgendamentoPorMedico(Medico medico, DateTime inicio, Duration duracao) {
		return new Agendamento(Tipo.MEDICO, medico, inicio, duracao);
	}

	/**
	 * Retorna um novo Agendamento criado a partir de uma especialidade medica.
	 * @param medico Medico marcado para o agendamento.
	 * @param inicio Inicio do horario a ser agendado.
	 * @param duracao Duracao do horario a ser agendado.
	 * @return Um novo agendamento criado a partir da especialidade.
	 */
	public static Agendamento criarAgendamentoPorEspecialidade(Medico medico, DateTime inicio, Duration duracao) {
		return new Agendamento(Tipo.ESPECIALIDADE, medico, inicio, duracao);
	}
	
	/**
	 * Retorna um novo Agendamento em espera que eh criado recebendo um medico.
	 * @param Medico
	 */
	public static Agendamento criarEsperaPorMedico(Medico medico, LocalDate expiraEm) {
		return new Agendamento(medico, expiraEm);	
	}
	
	/**
	 * Retorna um novo Agendamento em espera que eh criado recebendo uma especialidade.
	 * @param Especialidade.
	 */
	public static Agendamento criarEsperaPorEspecialidade(Especialidade especialidade, LocalDate expiraEm) {
		return new Agendamento(especialidade, expiraEm);	
	}
	
	/**
	 * Construtor de agendamento em espera que recebe um medico.
	 * @param medico Medico a ser marcado no agendamento.
	 * @param expiraEm Data em que o agendamento em espera irar expirar.
	 */
	private Agendamento(Medico medico, LocalDate expiraEm) {
		Validate.notNull(medico, "Médico não pode ser nulo.");
		Validate.notNull(expiraEm, "Data de expiração não pode ser nula.");
		Validate.isTrue(expiraEm.isAfter(new LocalDate()), "Data de expiração deve ser no futuro.");
		this.tipo = Tipo.ESPERA_MEDICO;
		this.medico = medico;
		this.expiraEm = expiraEm;
		this.especialidade = null;
		this.inicio = null;
		this.duracao = null;
	}
	
	/**
	 * Construtor de agendamento em espera que recebe uma especialidade.
	 * @param medico Medico a ser marcado no agendamento.
	 * @param expiraEm Data em que o agendamento em espera irar expirar.
	 */
	private Agendamento(Especialidade especialidade, LocalDate expiraEm) {
		Validate.notNull(especialidade, "Especialidade não pode ser nulo.");
		Validate.notNull(expiraEm, "Data de expiração não pode ser nula.");
		Validate.isTrue(expiraEm.isAfter(new LocalDate()), "Data de expiração deve ser no futuro.");
		this.tipo = Tipo.ESPERA_ESPECIALIDADE;
		this.especialidade = especialidade;
		this.expiraEm = expiraEm;
		this.medico = null;
		this.inicio = null;
		this.duracao = null;
	}

	/**
	 * Construtor de agendamento que recebe o Tipo de agendamento.
	 * @param tipo Tipo do agendamento.
	 * @param medico Medico do agendamento.
	 * @param inicio Inicio do horario a ser agendado.
	 * @param duracao Duracao do horario a ser agendado.
	 */
	private Agendamento(Tipo tipo, Medico medico, DateTime inicio, Duration duracao) {
		Validate.notNull(tipo, "O Tipo não pode ser nulo.");
		Validate.notNull(medico, "O Médico não pode ser nulo.");
		Validate.notNull(inicio, "O inicio não pode ser nulo.");
		Validate.notNull(duracao, "A duração não pode ser nula.");
		this.tipo = tipo;
		this.medico = medico;
		this.inicio = inicio;
		this.duracao = duracao;
		this.especialidade = medico.getEspecialidade();
		this.expiraEm = null;
	}

}